package com.example.opengldemo.jbulletJava;

import java.util.ArrayList;

import android.opengl.GLES30;
import android.opengl.GLSurfaceView;
import android.view.MotionEvent;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import javax.vecmath.Vector3f;

import com.bulletphysics.collision.broadphase.AxisSweep3;
import com.bulletphysics.collision.dispatch.CollisionConfiguration;
import com.bulletphysics.collision.dispatch.CollisionDispatcher;
import com.bulletphysics.collision.dispatch.DefaultCollisionConfiguration;
import com.bulletphysics.dynamics.DiscreteDynamicsWorld;
import com.bulletphysics.dynamics.constraintsolver.SequentialImpulseConstraintSolver;

import android.content.Context;

import com.bulletphysics.collision.shapes.CollisionShape;
import com.bulletphysics.collision.shapes.SphereShape;
import com.bulletphysics.collision.shapes.StaticPlaneShape;
import com.example.opengldemo.R;
import com.example.opengldemo.util.LoadUtil;
import com.example.opengldemo.util.MatrixState;
import com.example.opengldemo.util.ShaderManager;
import com.example.opengldemo.util.TextureUtil;

import static com.example.opengldemo.jbulletJava.Constant.*;

class MySurfaceView7 extends GLSurfaceView {
    private SceneRenderer mRenderer;
    final float LIGHT_Y = 70;
    float mPreviousX;
    float mPreviousY;
    float preNanoTime;
    boolean isMoveFlag = false;
    float cx = 0;
    float cy = 1;
    float cz = 6;
    float tx = 0;
    float tz = -10;
    double tempRadians = 0;
    double upRadians = 0;
    float left;
    float right;
    float top;
    float bottom;
    float near;
    float far;
    ArrayList<LoadedObjectVertexNormal> lovnList = new ArrayList<LoadedObjectVertexNormal>();
    int checkedIndex = -1;

    enum Area {LU, RU, LD, RD, NONE}
    Area currArea = Area.NONE;
    boolean areaTouch = false;
    AreaTouchThread areaTouchThread;

    Vector3f cameraCircleCenter = new Vector3f(0, 0, 0);

    DiscreteDynamicsWorld dynamicsWorld;
    CollisionShape ballShape;
    CollisionShape planeShape;
    Sample7_3_Activity activity;
    LoadedObjectVertexNormal[] loadedModels = new LoadedObjectVertexNormal[7];
    LoadedObjectVertexNormal[] bodyForDraws = new LoadedObjectVertexNormal[BodyPartIndex.BODYPART_COUNT.ordinal()];

    public MySurfaceView7(Context context) {
        super(context);
        this.activity = (Sample7_3_Activity) context;
        this.setEGLContextClientVersion(3);

        initWorld();
        mRenderer = new SceneRenderer();
        setRenderer(mRenderer);
        setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
    }

    public void initWorld() {

        CollisionConfiguration collisionConfiguration = new DefaultCollisionConfiguration();
        CollisionDispatcher dispatcher = new CollisionDispatcher(collisionConfiguration);
        Vector3f worldAabbMin = new Vector3f(-10000, -10000, -10000);
        Vector3f worldAabbMax = new Vector3f(10000, 10000, 10000);
        int maxProxies = 1024;
        AxisSweep3 overlappingPairCache = new AxisSweep3(worldAabbMin, worldAabbMax, maxProxies);
        SequentialImpulseConstraintSolver solver = new SequentialImpulseConstraintSolver();

        dynamicsWorld = new DiscreteDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration);

        dynamicsWorld.setGravity(new Vector3f(0, -30, 0));

        ballShape = new SphereShape(1);

        planeShape = new StaticPlaneShape(new Vector3f(0, 1, 0), 0);
    }

    public void loadCapsules() {
        LoadUtil.getLoadObj( MySurfaceView7.this,"jbullet_java/head.obj",
                (mv, vertexArray, normalArray, texcoorsArray) ->
                        loadedModels[0] = new LoadedObjectVertexNormal(mv, vertexArray, normalArray));

        LoadUtil.getLoadObj(MySurfaceView7.this,"jbullet_java/spine.obj",
                (mv, vertexArray, normalArray, texcoorsArray) ->
                        loadedModels[1] = new LoadedObjectVertexNormal(mv, vertexArray, normalArray));

        LoadUtil.getLoadObj( MySurfaceView7.this,"jbullet_java/pelvis.obj",
                (mv, vertexArray, normalArray, texcoorsArray) ->
                        loadedModels[2] = new LoadedObjectVertexNormal(mv, vertexArray, normalArray));

        LoadUtil.getLoadObj( MySurfaceView7.this,"jbullet_java/upper_arm.obj",
                (mv, vertexArray, normalArray, texcoorsArray) ->
                        loadedModels[3] = new LoadedObjectVertexNormal(mv, vertexArray, normalArray));

        LoadUtil.getLoadObj(MySurfaceView7.this,"jbullet_java/lower_arm.obj",
                (mv, vertexArray, normalArray, texcoorsArray) ->
                        loadedModels[4] = new LoadedObjectVertexNormal(mv, vertexArray, normalArray));

        LoadUtil.getLoadObj( MySurfaceView7.this,"jbullet_java/upper_leg.obj",
                (mv, vertexArray, normalArray, texcoorsArray) ->
                        loadedModels[5] = new LoadedObjectVertexNormal(mv, vertexArray, normalArray));

        LoadUtil.getLoadObj(MySurfaceView7.this,"jbullet_java/lower_leg.obj",
                (mv, vertexArray, normalArray, texcoorsArray) ->
                        loadedModels[6] = new LoadedObjectVertexNormal(mv, vertexArray, normalArray));

    }

    public void initBodyForDraws() {
        bodyForDraws[BodyPartIndex.BODYPART_HEAD.ordinal()] = loadedModels[0];
        bodyForDraws[BodyPartIndex.BODYPART_SPINE.ordinal()] = loadedModels[1];
        bodyForDraws[BodyPartIndex.BODYPART_PELVIS.ordinal()] = loadedModels[2];
        bodyForDraws[BodyPartIndex.BODYPART_RIGHT_UPPER_ARM.ordinal()] = loadedModels[3];
        bodyForDraws[BodyPartIndex.BODYPART_LEFT_UPPER_ARM.ordinal()] = loadedModels[3].clone();
        bodyForDraws[BodyPartIndex.BODYPART_LEFT_LOWER_ARM.ordinal()] = loadedModels[4];
        bodyForDraws[BodyPartIndex.BODYPART_RIGHT_LOWER_ARM.ordinal()] = loadedModels[4].clone();
        bodyForDraws[BodyPartIndex.BODYPART_RIGHT_UPPER_LEG.ordinal()] = loadedModels[5];
        bodyForDraws[BodyPartIndex.BODYPART_LEFT_UPPER_LEG.ordinal()] = loadedModels[5].clone();
        bodyForDraws[BodyPartIndex.BODYPART_RIGHT_LOWER_LEG.ordinal()] = loadedModels[6];
        bodyForDraws[BodyPartIndex.BODYPART_LEFT_LOWER_LEG.ordinal()] = loadedModels[6].clone();
        for (int i = 0; i < bodyForDraws.length; i++) {
            lovnList.add(bodyForDraws[i]);
        }
    }

    float x;
    float y;

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        x = event.getX();
        y = event.getY();
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                mPreviousX = event.getX();
                mPreviousY = event.getY();
                preNanoTime = System.nanoTime();
                checkedIndex = -1;
                float[] AB = IntersectantUtil.calculateABPosition
                        (
                                x,
                                y,
                                Sample7_3_Activity.screenWidth,
                                Sample7_3_Activity.screenHeight,
                                left,
                                top,
                                near,
                                far
                        );


                MyVector3f start = new MyVector3f(AB[0], AB[1], AB[2]);
                MyVector3f end = new MyVector3f(AB[3], AB[4], AB[5]);
                MyVector3f dir = end.minus(start);
                int tmpIndex = -1;
                float minTime = 1;
                for (int i = 0; i < lovnList.size(); i++) {
                    AABB3 box = lovnList.get(i).getCurrBox();
                    float t = box.rayIntersect(start, dir, null);
                    if (t <= minTime) {
                        minTime = t;
                        tmpIndex = i;
                    }
                }
                checkedIndex = tmpIndex;
                changeObj(checkedIndex);

                break;
            case MotionEvent.ACTION_MOVE:

                if (x - mPreviousX >= 10.0f || x - mPreviousX <= -10.0f) {
                    isMoveFlag = true;
                }
                if (isMoveFlag)
                {
                    if (checkedIndex != -1) {

                        LoadedObjectVertexNormal lovo = lovnList.get(checkedIndex);
                        lovo.isPicked = true;

                        float[] nearXY = IntersectantUtil.calculateABPosition(
                                x, y,
                                Sample7_3_Activity.screenWidth, Sample7_3_Activity.screenHeight,
                                left, top,
                                near, far
                        );

                        float[] nearPreXY = IntersectantUtil.calculateABPosition(
                                mPreviousX, mPreviousY,
                                Sample7_3_Activity.screenWidth, Sample7_3_Activity.screenHeight,
                                left, top,
                                near, far
                        );

                        Vector3f currPivot = lovo.p2p.getPivotInB(new Vector3f());

                        Vector3f dir1 = new Vector3f(nearXY[0] - nearPreXY[0], nearXY[1] - nearPreXY[1], nearXY[2] - nearPreXY[2]);
                        float vFactor = 0.5f;
                        dir1.set(dir1.x * vFactor, dir1.y * vFactor, dir1.z * vFactor);
                        currPivot.add(dir1);
                        lovo.p2p.setPivotB(currPivot);
                    }
                }
                break;
            case MotionEvent.ACTION_UP:

                isMoveFlag = false;
                if (checkedIndex != -1) {
                    LoadedObjectVertexNormal lovo = lovnList.get(checkedIndex);
                    lovo.removePickedConstraint();
                    checkedIndex = -1;
                }
                if (checkedIndex == -1 && currArea != Area.NONE) {
                    currArea = Area.NONE;
                    areaTouch = false;
                    upRadians = tempRadians;
                }
                break;
        }
        return true;
    }


    public void changeObj(int index) {
        if (index != -1) {
            LoadedObjectVertexNormal lovo = lovnList.get(index);
            lovo.body.activate();
            lovo.addPickedConstraint();
        } else {

            if (0 < x && x < Sample7_3_Activity.screenWidth / 2 && 0 < y && y < Sample7_3_Activity.screenHeight / 2) {
                currArea = Area.LU;
            } else if (Sample7_3_Activity.screenWidth / 2 < x && 0 < y && y < Sample7_3_Activity.screenHeight / 2) {
                currArea = Area.RU;
            } else if (0 < x && x < Sample7_3_Activity.screenWidth / 2 && y > Sample7_3_Activity.screenHeight / 2) {
                currArea = Area.LD;
            } else if (x > Sample7_3_Activity.screenWidth / 2 && y > Sample7_3_Activity.screenHeight / 2) {
                currArea = Area.RD;
            }
            areaTouch = true;
            if (areaTouchThread == null || !areaTouchThread.isAlive()) {
                areaTouchThread = new AreaTouchThread();
                areaTouchThread.start();
            }
        }
    }

    private class SceneRenderer implements Renderer {
        int floorTextureId;
        TexFloor floor;
        Doll doll;

        public void onDrawFrame(GL10 gl) {


            GLES30.glClear(GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);


            MatrixState.setLightLocation((float) (LIGHT_Y * Math.sin(tempRadians)), 30, (float) (LIGHT_Y * Math.cos(tempRadians)));


            cx = (float) (25 * Math.sin(tempRadians) + cameraCircleCenter.x);
            cz = (float) (25 * Math.cos(tempRadians) + cameraCircleCenter.z);
            tx = (float) (10 * Math.sin(tempRadians) + cameraCircleCenter.x);
            tz = (float) (10 * Math.cos(tempRadians) + cameraCircleCenter.z);
            MatrixState.setCamera(cx, cy, cz, tx, 0f, tz, 0f, 1.0f, 0.0f);

            MatrixState.pushMatrix();
            MatrixState.copyMVMatrix();
            doll.drawSelf(checkedIndex);

            MatrixState.pushMatrix();
            floor.drawSelf(floorTextureId);
            MatrixState.popMatrix();

            MatrixState.popMatrix();
        }

        public void onSurfaceChanged(GL10 gl, int width, int height) {

            GLES30.glViewport(0, 0, width, height);

            float ratio = (float) width / height;
            left = right = ratio;
            top = bottom = 1;
            near = 2;
            far = 100;
            MatrixState.setProjectFrustum(-left, right, -bottom, top, near, far);
        }

        public void onSurfaceCreated(GL10 gl, EGLConfig config) {

            GLES30.glClearColor(0, 0, 0, 0);

            GLES30.glEnable(GL10.GL_DEPTH_TEST);

            GLES30.glEnable(GL10.GL_CULL_FACE);


            MatrixState.setInitStack();
            ShaderManager.loadDefalutShader(getResources());
            ShaderManager.compileDefalutShader();
            loadCapsules();
            initBodyForDraws();


            floorTextureId = TextureUtil.initTextureRepeat(R.drawable.f6 , getResources());
            doll = new Doll(MySurfaceView7.this, dynamicsWorld, bodyForDraws);
            floor = new TexFloor(ShaderManager.getTextureShaderProgram(), 80 * Constant.UNIT_SIZE, -Constant.UNIT_SIZE, planeShape, dynamicsWorld);
            new Thread() {
                public void run() {
                    while (true) {
                        try {

                            dynamicsWorld.stepSimulation(TIME_STEP, MAX_SUB_STEPS);
                            Thread.sleep(20);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
        }
    }

    class AreaTouchThread extends Thread {
        float time = 0;
        float linearV = 0.1f;
        float angularV = 2;

        public void run() {
            while (areaTouch) {
                System.out.println("run");
                System.out.println(currArea.ordinal());

                time++;
                if (currArea == Area.LU) {
                    tempRadians = upRadians - Math.toRadians(time * angularV);
                }
                if (currArea == Area.RU) {
                    tempRadians = upRadians + Math.toRadians(time * angularV);
                }
                if (currArea == Area.LD) {
                    float moveLength = time * linearV;
                    cameraCircleCenter.x = (float) (cameraCircleCenter.x - moveLength * Math.sin(tempRadians));
                    cameraCircleCenter.z = (float) (cameraCircleCenter.z - moveLength * Math.cos(tempRadians));
                }
                if (currArea == Area.RD) {
                    float moveLength = time * linearV;
                    cameraCircleCenter.x = (float) (cameraCircleCenter.x + moveLength * Math.sin(tempRadians));
                    cameraCircleCenter.z = (float) (cameraCircleCenter.z + moveLength * Math.cos(tempRadians));
                }
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
